package nkr1pt.popkorn.download;

import java.io.*;

import java.security.*;
import java.security.cert.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.*;

/**
 * Installs certificates to the default Java keystore if needed.
 * These certificates are used for authentication and authorization purposes
 * on SSL connections.
 *
 * @author Kristof Vanhaeren
 */
//TODO handle connection through proxy
//TODO unstable, see for fix http://www.mail-archive.com/httpclient-users@hc.apache.org/msg00481.html
public class CertificateInstaller {

    private static final Logger logger = Logger.getLogger(CertificateInstaller.class.getName());

    public static void install(String host, int port, String passwd) throws Exception {
        port = port == 0 ? 443 : port;
        passwd = passwd == null ? "changeit" : passwd;

        char SEP = File.separatorChar;
        File cacertsFile = new File(System.getProperty("java.home") + SEP
                + "lib" + SEP + "security" + SEP + "cacerts");
        logger.log(Level.INFO, "Loading KeyStore {0}...", cacertsFile);
        InputStream in = new FileInputStream(cacertsFile);
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(in, passwd.toCharArray());
        in.close();

        SSLContext context = SSLContext.getInstance("TLS");
        TrustManagerFactory tmf =
                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
        SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
        context.init(null, new TrustManager[]{tm}, null);
        SSLSocketFactory factory = context.getSocketFactory();

        logger.log(Level.INFO, "Opening connection to {0}:{1}...", new Object[]{host, port});
        SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
        socket.setSoTimeout(10000);
        try {
            logger.log(Level.INFO, "Starting SSL handshake...");
            socket.startHandshake();
            socket.close();
            logger.log(Level.INFO, "No errors, certificate is already trusted");
            return;
        } catch (SSLException e) {
            e.printStackTrace(System.out);
        }

        X509Certificate[] chain = tm.chain;
        if (chain == null) {
            logger.log(Level.INFO, "Could not obtain server certificate chain");
            return;
        }

        BufferedReader reader =
                new BufferedReader(new InputStreamReader(System.in));
        logger.log(Level.INFO, "Server sent {0} certificate(s):", chain.length);
        MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        for (int i = 0; i < chain.length; i++) {
            X509Certificate cert = chain[i];
            logger.log(Level.INFO, " {0} Subject {1}", new Object[]{i + 1, cert.getSubjectDN()});
            logger.log(Level.INFO, "   Issuer  {0}", cert.getIssuerDN());
            sha1.update(cert.getEncoded());
            logger.log(Level.INFO, "   sha1    {0}", toHexString(sha1.digest()));
            md5.update(cert.getEncoded());
            logger.log(Level.INFO, "   md5     {0}", toHexString(md5.digest()));

            String alias = host + "-" + (i + 1);
            ks.setCertificateEntry(alias, cert);

            OutputStream out = new FileOutputStream(cacertsFile);
            ks.store(out, passwd.toCharArray());
            out.close();

            logger.log(Level.INFO, cert.toString());
            logger.log(Level.INFO, "Added certificate to keystore using alias ''{0}''", alias);
        }

    }
    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

    private static String toHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 3);
        for (int b : bytes) {
            b &= 0xff;
            sb.append(HEXDIGITS[b >> 4]);
            sb.append(HEXDIGITS[b & 15]);
            sb.append(' ');
        }
        return sb.toString();
    }

    private static class SavingTrustManager implements X509TrustManager {

        private final X509TrustManager tm;
        private X509Certificate[] chain;

        SavingTrustManager(X509TrustManager tm) {
            this.tm = tm;
        }

        public X509Certificate[] getAcceptedIssuers() {
            throw new UnsupportedOperationException();
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
            throw new UnsupportedOperationException();
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
            this.chain = chain;
            tm.checkServerTrusted(chain, authType);
        }
    }
} 
